<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>操作日志列表</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    <style>
        .table th, .table td { vertical-align: middle; }
        .pagination .page-link { min-width: 36px; text-align: center; }
        .pagination-summary { margin-top: 16px; color: #888; }
        .search-bar input, .search-bar select { min-width: 120px; margin-right: 8px; }
        .search-bar { margin-bottom: 16px; }
        .pagination-bar {
            font-size: 13px;
        }
        .pagination-sm .page-link {
            min-width: 32px;
            height: 32px;
            padding: 0 8px;
            border-radius: 4px !important;
            margin: 0 2px;
            display: flex;
            align-items: center;
            justify-content: center;
            line-height: 1;
        }
        .pagination-sm .active .page-link {
            background: #1677ff;
            color: #fff;
            border-color: #1677ff;
        }
        .form-select-sm, .form-control-sm {
            font-size: 13px;
            height: 32px;
            padding: 0 8px;
        }
    </style>
</head>
<body>
{% verbatim %}
<div id="app" class="container-fluid mt-4">
    <div class="card">
        <div class="card-body">
            <!-- 搜索栏 -->
            <form class="row search-bar align-items-center g-2 mb-3">
                <div class="col-auto">
                    <input type="text" class="form-control" placeholder="登录地址" v-model="search.login_ip">
                </div>
                <div class="col-auto">
                    <input type="text" class="form-control" placeholder="用户名" v-model="search.user_name">
                </div>
                <div class="col-auto">
                    <select class="form-select" v-model="search.status">
                        <option value="">状态</option>
                        <option value="成功">成功</option>
                        <option value="失败">失败</option>
                    </select>
                </div>
                <div class="col-auto">
                    <input type="text" class="form-control" placeholder="登录标签" v-model="search.login_tag">
                </div>
                <div class="col-auto">
                    <input type="text" class="form-control" placeholder="操作信息" v-model="search.operate_info">
                </div>
                <div class="col-auto">
                    <input type="date" class="form-control" v-model="search.start_date">
                </div>
                <div class="col-auto">
                    <input type="date" class="form-control" v-model="search.end_date">
                </div>
                <div class="col-auto">
                    <button type="button" class="btn btn-primary" @click.prevent="filterData">查询</button>
                </div>
            </form>
            <!-- 表格 -->
            <div class="table-responsive">
                <table class="table table-hover align-middle">
                    <thead>
                        <tr>
                            <th><input type="checkbox" v-model="selectAll" @change="toggleAll"></th>
                            <th>访问编号</th>
                            <th>用户名</th>
                            <th>登录地址</th>
                            <th>登录地点</th>
                            <th>浏览器</th>
                            <th>操作系统</th>
                            <th>登录标签</th>
                            <th>操作信息</th>
                            <th>登录状态</th>
                            <th>登录日期</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="item in pagedData" :key="item.id">
                            <td><input type="checkbox" v-model="selected" :value="item.id"></td>
                            <td>{{ item.id }}</td>
                            <td>{{ item.user_name }}</td>
                            <td>{{ item.login_ip }}</td>
                            <td>{{ item.login_location }}</td>
                            <td>{{ item.browser }}</td>
                            <td>{{ item.os }}</td>
                            <td>{{ item.login_tag }}</td>
                            <td>{{ item.operate_info }}</td>
                            <td>
                                <span :class="['badge', item.status === '成功' ? 'bg-success' : 'bg-danger']">{{ item.status }}</span>
                            </td>
                            <td>{{ item.login_date }}</td>
                            <td><a href="#" class="text-primary">详情</a></td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <!-- 分页和统计 -->
            <div class="d-flex justify-content-end align-items-center mt-3 flex-wrap">
                <div class="d-flex align-items-center pagination-bar">
                    <span class="text-secondary small">共 {{ filteredData.length }} 条</span>
                    <select v-model.number="pageSize" class="form-select form-select-sm w-auto mx-1 me-3"
                            style="min-width: 80px;white-space:nowrap;">
                        <option :value="5">5条/页</option>
                        <option :value="10">10条/页</option>
                    </select>
                    <ul class="pagination pagination-sm mb-0 align-items-center">
                        <li class="page-item" :class="{disabled: currentPage === 1}">
                            <a class="page-link" href="#" @click.prevent="changePage(currentPage-1)">&lt;</a>
                        </li>
                        <li class="page-item" v-for="page in visiblePages" :key="page"
                            :class="{active: currentPage === page, disabled: page === '...'}">
                            <a v-if="page !== '...'" class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
                            <span v-else class="page-link">...</span>
                        </li>
                        <li class="page-item" :class="{disabled: currentPage === totalPages}">
                            <a class="page-link" href="#" @click.prevent="changePage(currentPage+1)">&gt;</a>
                        </li>
                    </ul>
                    <span class="ms-2 small">前往</span>
                    <input type="number" min="1" :max="totalPages" v-model.number="gotoPage" @keyup.enter="jumpToPage"
                           class="form-control form-control-sm d-inline-block mx-1" style="width:50px;">
                    <span class="small">页</span>
                    <button class="btn btn-outline-secondary btn-sm ms-1" @click="jumpToPage">跳转</button>
                </div>
            </div>
        </div>
    </div>
</div>
{% endverbatim %}
<script src="/static/js/vue.global.js"></script>
<script>
const { createApp, ref, computed, watch } = Vue;

// 造40条测试数据
const mockData = Array.from({length: 40}, (_, i) => ({
    id: 60 - i,
    user_name: 'admin',
    login_ip: '127.0.0.1',
    login_location: '本机地址',
    browser: 'Chrome 89.0.4389',
    os: 'Mac OS X 10.14.5',
    login_tag: '成功',
    operate_info: '登录成功',
    status: i % 7 === 0 ? '失败' : '成功',
    login_date: `2021-05-05 15:${String(58 - i).padStart(2, '0')}:29`
}));

createApp({
    setup() {
        const data = ref([...mockData]);
        const search = ref({
            login_ip: '',
            user_name: '',
            status: '',
            login_tag: '',
            operate_info: '',
            start_date: '',
            end_date: ''
        });
        const filteredData = ref([...mockData]);
        const selected = ref([]);
        const selectAll = ref(false);
        const pageSize = ref(5);
        const currentPage = ref(1);
        const gotoPage = ref(1);

        const totalPages = computed(() => Math.ceil(filteredData.value.length / pageSize.value));
        const pagedData = computed(() => {
            const start = (currentPage.value - 1) * pageSize.value;
            return filteredData.value.slice(start, start + pageSize.value);
        });
        const visiblePages = computed(() => {
            let pages = [];
            let total = totalPages.value;
            let cur = currentPage.value;
            if (total <= 7) {
                for (let i = 1; i <= total; i++) pages.push(i);
            } else {
                if (cur <= 4) {
                    pages = [1,2,3,4,5,'...',total];
                } else if (cur >= total - 3) {
                    pages = [1,'...',total-4,total-3,total-2,total-1,total];
                } else {
                    pages = [1,'...',cur-1,cur,cur+1,'...',total];
                }
            }
            return pages;
        });

        function filterData() {
            filteredData.value = data.value.filter(item => {
                return (!search.value.login_ip || item.login_ip.includes(search.value.login_ip)) &&
                    (!search.value.user_name || item.user_name.includes(search.value.user_name)) &&
                    (!search.value.status || item.status === search.value.status) &&
                    (!search.value.login_tag || item.login_tag.includes(search.value.login_tag)) &&
                    (!search.value.operate_info || item.operate_info.includes(search.value.operate_info)) &&
                    (!search.value.start_date || item.login_date >= search.value.start_date) &&
                    (!search.value.end_date || item.login_date <= search.value.end_date);
            });
            currentPage.value = 1;
        }

        function changePage(page) {
            if (typeof page === 'string') return;
            if (page >= 1 && page <= totalPages.value) {
                currentPage.value = page;
            }
        }

        function toggleAll() {
            if (selectAll.value) {
                selected.value = pagedData.value.map(item => item.id);
            } else {
                selected.value = [];
            }
        }

        function jumpToPage() {
            if (gotoPage.value >= 1 && gotoPage.value <= totalPages.value) {
                currentPage.value = gotoPage.value;
            }
        }

        watch(pageSize, () => {
            currentPage.value = 1;
        });

        watch(selected, () => {
            selectAll.value = pagedData.value.length > 0 && pagedData.value.every(item => selected.value.includes(item.id));
        });

        watch(currentPage, (val) => {
            gotoPage.value = val;
        });

        return {
            search,
            filteredData,
            pagedData,
            pageSize,
            currentPage,
            totalPages,
            visiblePages,
            filterData,
            changePage,
            selected,
            selectAll,
            toggleAll,
            gotoPage,
            jumpToPage
        };
    }
}).mount('#app');
</script>
</body>
</html> 